| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558 |
- <template>
- <div class="admin--popup-form">
- <div v-if="isLoading" class="admin--loading">데이터를 불러오는 중...</div>
- <form v-else @submit.prevent="handleSubmit" class="admin--form">
- <!-- 형태 -->
- <div class="admin--form-group">
- <label class="admin--form-label"
- >형태 <span class="admin--required">*</span></label
- >
- <div class="admin--radio-group">
- <label class="admin--radio-label">
- <input v-model="formData.type" type="radio" value="html" name="type" />
- <span>HTML</span>
- </label>
- <label class="admin--radio-label">
- <input v-model="formData.type" type="radio" value="image" name="type" />
- <span>단일페이지</span>
- </label>
- </div>
- </div>
- <!-- 사이트 선택 -->
- <div class="admin--form-group">
- <label class="admin--form-label"
- >사이트 <span class="admin--required">*</span></label
- >
- <select v-model="formData.site" class="admin--form-select" required>
- <option value="ford">포드</option>
- <option value="lincoln">링컨</option>
- </select>
- </div>
- <!-- 제목 -->
- <div class="admin--form-group">
- <label class="admin--form-label"
- >제목 <span class="admin--required">*</span></label
- >
- <input
- v-model="formData.title"
- type="text"
- class="admin--form-input"
- placeholder="팝업 제목을 입력하세요"
- required
- />
- </div>
- <!-- 시작일/종료일 -->
- <div class="admin--form-group">
- <label class="admin--form-label"
- >시작일/종료일 <span class="admin--required">*</span></label
- >
- <div class="admin--date-range">
- <DatePicker
- v-model="formData.start_date"
- placeholder="시작일 선택"
- :max-date="formData.end_date"
- required
- />
- <span class="admin--date-separator">~</span>
- <DatePicker
- v-model="formData.end_date"
- placeholder="종료일 선택"
- :min-date="formData.start_date"
- required
- />
- </div>
- </div>
- <!-- 시작시간 (선택) -->
- <div class="admin--form-group">
- <label class="admin--form-label">시작시간 <span class="admin--hint">(미입력 시 시작일 00:00부터 바로 출력)</span></label>
- <div class="admin--time-group">
- <label class="admin--radio-label">
- <input v-model="formData.start_period" type="radio" value="AM" name="start_period" />
- <span>오전</span>
- </label>
- <label class="admin--radio-label">
- <input v-model="formData.start_period" type="radio" value="PM" name="start_period" />
- <span>오후</span>
- </label>
- <input
- v-model.number="formData.start_hour"
- type="number"
- class="admin--form-input admin--time-input"
- placeholder="시"
- min="1"
- max="12"
- />
- <span>:</span>
- <input
- v-model.number="formData.start_minute"
- type="number"
- class="admin--form-input admin--time-input"
- placeholder="분"
- min="0"
- max="59"
- />
- </div>
- </div>
- <!-- 종료시간 (선택) -->
- <div class="admin--form-group">
- <label class="admin--form-label">종료시간 <span class="admin--hint">(미입력 시 종료일 자정까지 노출)</span></label>
- <div class="admin--time-group">
- <label class="admin--radio-label">
- <input v-model="formData.end_period" type="radio" value="AM" name="end_period" />
- <span>오전</span>
- </label>
- <label class="admin--radio-label">
- <input v-model="formData.end_period" type="radio" value="PM" name="end_period" />
- <span>오후</span>
- </label>
- <input
- v-model.number="formData.end_hour"
- type="number"
- class="admin--form-input admin--time-input"
- placeholder="시"
- min="1"
- max="12"
- />
- <span>:</span>
- <input
- v-model.number="formData.end_minute"
- type="number"
- class="admin--form-input admin--time-input"
- placeholder="분"
- min="0"
- max="59"
- />
- </div>
- </div>
- <!-- 팝업창 사이즈 -->
- <div class="admin--form-group">
- <label class="admin--form-label"
- >팝업창 사이즈 <span class="admin--required">*</span></label
- >
- <div class="admin--size-group">
- <div class="admin--size-item">
- <label>가로</label>
- <input
- v-model.number="formData.width"
- type="number"
- class="admin--form-input"
- placeholder="800"
- min="100"
- required
- />
- <span>px</span>
- </div>
- <div class="admin--size-item">
- <label>세로</label>
- <input
- v-model.number="formData.height"
- type="number"
- class="admin--form-input"
- placeholder="600"
- min="100"
- required
- />
- <span>px</span>
- </div>
- </div>
- </div>
- <!-- 팝업창 위치 -->
- <div class="admin--form-group">
- <label class="admin--form-label"
- >팝업창 위치 <span class="admin--required">*</span></label
- >
- <div class="admin--size-group">
- <div class="admin--size-item">
- <label>TOP</label>
- <input
- v-model.number="formData.position_top"
- type="number"
- class="admin--form-input"
- placeholder="100"
- min="0"
- required
- />
- <span>px</span>
- </div>
- <div class="admin--size-item">
- <label>LEFT</label>
- <input
- v-model.number="formData.position_left"
- type="number"
- class="admin--form-input"
- placeholder="100"
- min="0"
- required
- />
- <span>px</span>
- </div>
- </div>
- </div>
- <!-- 쿠키설정 -->
- <div class="admin--form-group">
- <label class="admin--form-label">쿠키설정</label>
- <div class="admin--radio-group">
- <label class="admin--radio-label">
- <input
- v-model="formData.cookie_setting"
- type="radio"
- value="today"
- name="cookie_setting"
- />
- <span>오늘 하루 창 띄우지 않음</span>
- </label>
- <label class="admin--radio-label">
- <input
- v-model="formData.cookie_setting"
- type="radio"
- value="forever"
- name="cookie_setting"
- />
- <span>다시는 창을 띄우지 않음</span>
- </label>
- <label class="admin--radio-label">
- <input
- v-model="formData.cookie_setting"
- type="radio"
- value="none"
- name="cookie_setting"
- />
- <span>사용 안 함</span>
- </label>
- </div>
- </div>
- <!-- 출력내용 (HTML) -->
- <div v-if="formData.type === 'html'" class="admin--form-group">
- <label class="admin--form-label"
- >출력내용 <span class="admin--required">*</span></label
- >
- <SunEditor
- v-model="formData.content"
- height="400px"
- placeholder="팝업 내용을 입력하세요"
- />
- </div>
- <!-- 출력내용 (이미지) -->
- <div v-if="formData.type === 'image'" class="admin--form-group">
- <label class="admin--form-label"
- >이미지 첨부 <span class="admin--required">*</span></label
- >
- <input
- type="file"
- accept="image/*"
- class="admin--form-file"
- @change="handleImageUpload"
- />
- <div v-if="imagePreview || formData.image_url" class="admin--image-preview">
- <img :src="imagePreview || formData.image_url" alt="미리보기" />
- <button type="button" class="admin--btn-remove-image" @click="removeImage">
- 삭제
- </button>
- </div>
- </div>
- <!-- 링크 URL (단일페이지일 경우) -->
- <div v-if="formData.type === 'image'" class="admin--form-group">
- <label class="admin--form-label">링크 URL</label>
- <input
- v-model="formData.link_url"
- type="url"
- class="admin--form-input"
- placeholder="https://example.com"
- />
- </div>
- <!-- 링크 타겟 (단일페이지일 경우) -->
- <div v-if="formData.type === 'image'" class="admin--form-group">
- <label class="admin--form-label">링크 열기 방식</label>
- <div class="admin--radio-group">
- <label class="admin--radio-label">
- <input
- v-model="formData.link_target"
- type="radio"
- value="_blank"
- name="link_target"
- />
- <span>새창</span>
- </label>
- <label class="admin--radio-label">
- <input
- v-model="formData.link_target"
- type="radio"
- value="_self"
- name="link_target"
- />
- <span>현재창</span>
- </label>
- </div>
- </div>
- <!-- 버튼 영역 -->
- <div class="admin--form-actions">
- <button type="submit" class="admin--btn admin--btn-primary" :disabled="isSaving">
- {{ isSaving ? "저장 중..." : "확인" }}
- </button>
- <button type="button" class="admin--btn admin--btn-secondary" @click="goToList">
- 목록
- </button>
- </div>
- <!-- 성공/에러 메시지 -->
- <div v-if="successMessage" class="admin--alert admin--alert-success">
- {{ successMessage }}
- </div>
- <div v-if="errorMessage" class="admin--alert admin--alert-error">
- {{ errorMessage }}
- </div>
- </form>
- </div>
- </template>
- <script setup>
- import { ref, onMounted } from "vue";
- import { useRoute, useRouter } from "vue-router";
- import SunEditor from "~/components/admin/SunEditor.vue";
- import DatePicker from "~/components/admin/DatePicker.vue";
- definePageMeta({
- layout: "admin",
- middleware: ["auth"],
- });
- const route = useRoute();
- const router = useRouter();
- const { get, put, upload } = useApi();
- const { getImageUrl } = useImage();
- const isLoading = ref(true);
- const isSaving = ref(false);
- const successMessage = ref("");
- const errorMessage = ref("");
- const imagePreview = ref(null);
- const imageFile = ref(null);
- const formData = ref({
- type: "html",
- site: "ford",
- title: "",
- start_date: "",
- end_date: "",
- start_period: "AM",
- start_hour: null,
- start_minute: null,
- end_period: "AM",
- end_hour: null,
- end_minute: null,
- width: 800,
- height: 600,
- position_top: 100,
- position_left: 100,
- cookie_setting: "none",
- content: "",
- image_url: "",
- link_url: "",
- link_target: "_blank",
- });
- // 12시간제 ↔ 24시간 "HH:MM:SS" 변환
- const toTime24 = (period, hour, minute) => {
- if (hour === null || hour === undefined || hour === "") return null;
- let h = Number(hour);
- const m = Number(minute || 0);
- if (isNaN(h) || h < 1 || h > 12) return null;
- if (period === "PM" && h < 12) h += 12;
- if (period === "AM" && h === 12) h = 0;
- return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:00`;
- };
- const fromTime24 = (timeStr) => {
- if (!timeStr) return { period: "AM", hour: null, minute: null };
- const [hStr, mStr] = String(timeStr).split(":");
- let h = Number(hStr);
- const m = Number(mStr || 0);
- if (isNaN(h)) return { period: "AM", hour: null, minute: null };
- const period = h >= 12 ? "PM" : "AM";
- const hour12 = h % 12 === 0 ? 12 : h % 12;
- return { period, hour: hour12, minute: m };
- };
- // 데이터 로드
- const loadPopup = async () => {
- isLoading.value = true;
- const id = route.params.id;
- const { data, error } = await get(`/basic/popup/${id}`);
- console.log("[Popup Edit] 데이터 로드 응답:", { data, error });
- // API 응답: { success: true, data: {...}, message }
- if (data?.success && data?.data) {
- const popup = data.data;
- const startParts = fromTime24(popup.start_time);
- const endParts = fromTime24(popup.end_time);
- formData.value = {
- type: popup.type || "html",
- site: popup.site || "ford",
- title: popup.title || "",
- start_date: popup.start_date || "",
- end_date: popup.end_date || "",
- start_period: startParts.period,
- start_hour: startParts.hour,
- start_minute: startParts.minute,
- end_period: endParts.period,
- end_hour: endParts.hour,
- end_minute: endParts.minute,
- width: popup.width || 800,
- height: popup.height || 600,
- position_top: popup.position_top || 100,
- position_left: popup.position_left || 100,
- cookie_setting: popup.cookie_setting || "none",
- content: popup.content || "",
- image_url: popup.image_url || "",
- link_url: popup.link_url || "",
- link_target: popup.link_target || "_blank",
- };
- // 이미지 미리보기는 새 이미지 업로드시에만 사용
- // 기존 이미지는 formData.image_url을 통해 getImageUrl()로 표시
- imagePreview.value = null;
- console.log("[Popup Edit] 이미지 URL:", popup.image_url);
- console.log("[Popup Edit] 데이터 로드 성공:", formData.value);
- } else {
- console.log("[Popup Edit] 데이터 로드 실패");
- }
- isLoading.value = false;
- };
- // 이미지 업로드
- const handleImageUpload = (event) => {
- const file = event.target.files[0];
- if (!file) return;
- if (!file.type.startsWith("image/")) {
- alert("이미지 파일만 업로드 가능합니다.");
- return;
- }
- imageFile.value = file;
- // 미리보기
- const reader = new FileReader();
- reader.onload = (e) => {
- imagePreview.value = e.target.result;
- };
- reader.readAsDataURL(file);
- };
- // 이미지 삭제
- const removeImage = () => {
- imagePreview.value = null;
- imageFile.value = null;
- formData.value.image_url = "";
- };
- // 폼 제출
- const handleSubmit = async () => {
- successMessage.value = "";
- errorMessage.value = "";
- // 유효성 검사
- if (!formData.value.title) {
- errorMessage.value = "제목을 입력하세요.";
- return;
- }
- if (!formData.value.start_date || !formData.value.end_date) {
- errorMessage.value = "시작일과 종료일을 선택하세요.";
- return;
- }
- if (formData.value.type === "html" && !formData.value.content) {
- errorMessage.value = "출력내용을 입력하세요.";
- return;
- }
- if (
- formData.value.type === "image" &&
- !imageFile.value &&
- !formData.value.image_url
- ) {
- errorMessage.value = "이미지를 첨부하세요.";
- return;
- }
- isSaving.value = true;
- try {
- let imageUrl = formData.value.image_url;
- // 이미지 업로드 (새로운 이미지가 있는 경우)
- if (formData.value.type === "image" && imageFile.value) {
- const formDataImage = new FormData();
- formDataImage.append("file", imageFile.value);
- const { data: uploadData, error: uploadError } = await upload(
- "/upload/image",
- formDataImage
- );
- console.log("[Popup Edit] 이미지 업로드 응답:", { uploadData, uploadError });
- if (uploadError || !uploadData?.success) {
- errorMessage.value = uploadError?.message || "이미지 업로드에 실패했습니다.";
- isSaving.value = false;
- return;
- }
- imageUrl = uploadData.data?.url || uploadData.data;
- }
- // 팝업 수정
- const submitData = {
- ...formData.value,
- image_url: imageUrl,
- start_time: toTime24(formData.value.start_period, formData.value.start_hour, formData.value.start_minute),
- end_time: toTime24(formData.value.end_period, formData.value.end_hour, formData.value.end_minute),
- };
- const id = route.params.id;
- const { data, error } = await put(`/basic/popup/${id}`, submitData);
- if (error || !data?.success) {
- errorMessage.value = error?.message || data?.message || "수정에 실패했습니다.";
- } else {
- successMessage.value = data.message || "팝업이 수정되었습니다.";
- setTimeout(() => {
- router.push("/site-manager/basic/popup");
- }, 1000);
- }
- } catch (error) {
- errorMessage.value = "서버 오류가 발생했습니다.";
- console.error("Save error:", error);
- } finally {
- isSaving.value = false;
- }
- };
- // 목록으로 이동
- const goToList = () => {
- router.push("/site-manager/basic/popup");
- };
- onMounted(() => {
- loadPopup();
- });
- </script>
|